// SPDX-License-Identifier: GPL-2.0
/*
 * lombo_gpadc.c - Lombo gpadc driver module
 *
 * Copyright (C) 2016-2018, LomboTech Co.Ltd.
 * Author: lomboswer <lomboswer@lombotech.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <linux/device.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/iio/iio.h>
#include <linux/input.h>
#include <linux/pm.h>
#include <linux/delay.h>
#include <linux/suspend.h>
#include <mach/debug.h>
#include <mach/common.h>
#include <linux/pm_runtime.h>
#include <linux/pm_clock.h>

#include "lombo_gpadc.h"

struct lombo_gpadc_dev *gpadc_dev;
int num = -1;

#ifdef CONFIG_LOMBO_THERMAL
long lombo_get_temp(int id, int slope, int offset)
{
	return csp_sensor_get_temp(id, gpadc_dev->base, slope, offset);
}
int lombo_calibration_offset(int id)
{
	return csp_calibration_offset(id);
}

int lombo_get_slope(int id)
{
	return csp_get_slope(id);
}
#endif  /* CONFIG_LOMBO_THERMAL */

static int gpadc_enable_clk(struct lombo_gpadc_dev *gpadc,
					struct device *gpadc_dev)
{
	int ret;

	ret = clk_prepare_enable(gpadc->clk_reset);
	if (ret) {
		dev_err(gpadc_dev,
			"clk_prepare_enable gpadc->clk_reset failed\n");
		return ret;
	}
#ifdef CONFIG_PM_GENERIC_DOMAINS
	ret = pm_clk_add_clk(gpadc_dev, gpadc->clk_gate);
	ret |=  pm_clk_add_clk(gpadc_dev, gpadc->gpadc_clk);
	pm_clk_resume(gpadc_dev);
#else
	ret = clk_prepare_enable(gpadc->clk_gate);
	if (ret) {
		dev_err(gpadc_dev,
			"clk_prepare_enable gpadc->clk_gate failed\n");
		return ret;
	}

	ret = clk_prepare_enable(gpadc->gpadc_clk);
	if (ret) {
		dev_err(gpadc_dev,
			"clk_prepare_enable gpadc->gpadc_clk failed\n");
		return ret;
	}
#endif
	return 0;
}

static int gpadc_disable_clk(struct lombo_gpadc_dev *gpadc,
				struct device *gpadc_dev)
{
#ifdef CONFIG_PM_GENERIC_DOMAINS
	pm_runtime_put_sync(gpadc_dev);
	pm_runtime_disable(gpadc_dev);
	pm_clk_remove_clk(gpadc_dev, gpadc->gpadc_clk);
	pm_clk_remove_clk(gpadc_dev, gpadc->clk_gate);
#else
	if (gpadc->gpadc_clk)
		clk_disable_unprepare(gpadc->gpadc_clk);
	if (gpadc->clk_gate)
		clk_disable_unprepare(gpadc->clk_gate);
#endif
	if (gpadc->clk_reset)
		clk_disable_unprepare(gpadc->clk_reset);
	return 0;
}

static void gpadc_module_set_en(void *base, u32 en)
{
	int i;

#ifndef CONFIG_ARCH_LOMBO_N7V1
	if (gpadc_dev->io_channel.chan_num != -1) {
		for (i = 0; i < gpadc_dev->io_channel.chan_num; i++)
			csp_ain_init_output(base,
					gpadc_dev->io_channel.channel[i], en);
	}

	csp_set_sample_time(base);
	csp_set_ldo(base);
#endif

	/* gpadc module enable */
	csp_gpadc_set_en(base, en);
	//csp_gpadc_set_cali_en(base, en);
	if (gpadc_dev->ctl_channel.ctl_num > 0)
		for (i = 0; i < gpadc_dev->ctl_channel.ctl_num; i++)
			csp_ain_set_th_data(base,
					gpadc_dev->ctl_channel.channel[i],
					gpadc_dev->vol_thre);

#ifdef CONFIG_ARCH_LOMBO_N7V1
	csp_gpadc_set_sda_en(base, en);
	/* enable gpadc channel */
	csp_auxin_set_en(base, 0, en);
	csp_auxin_set_en(base, 1, en);
	if (gpadc_dev->ctl_channel.ctl_num > 0) {
		for (i = 0; i < gpadc_dev->ctl_channel.ctl_num; i++)
			csp_gpadc_set_int_en(base,
					BIT(gpadc_dev->ctl_channel.channel[i]),
					en);
	}
#else
	/* ain enable, for n7v3 and n9v1 */
	csp_ain_set_sda_en(base, en);
	if (gpadc_dev->ctl_channel.ctl_num > 0) {
		for (i = 0; i < gpadc_dev->ctl_channel.ctl_num; i++) {
			csp_ain_set_int_en(base,
					gpadc_dev->ctl_channel.channel[i], en);
			csp_ain_set_en(base,
				AIN_X_EN_BIT(gpadc_dev->ctl_channel.channel[i]),
				en);
		}
	}
#endif

	/* tempareture sensor enable */
	csp_sensor_set_en(base, en);
	csp_sensor_set_sda_en(base, en);
}

static void gpadc_init(void *base)
{
	gpadc_module_set_en(base, 1);
}

static void gpadc_deinit(void *base)
{
	gpadc_module_set_en(base, 0);
}

static int lombo_gpadc_read_chan(struct lombo_gpadc_dev *gpadc_dev,
				struct iio_chan_spec const *chan)
{
	u32 val;
	void __iomem *base;
	int ch, max_ch;

	ch = chan->channel;
	base = gpadc_dev->base;

#ifdef CONFIG_ARCH_LOMBO_N7V1
	max_ch = 2;
#else
	max_ch = 8;
#endif

	if (chan->type == IIO_VOLTAGE) {
		if (ch < max_ch)
			val = csp_ain_get_data(base, ch);
		else {
			pr_warn("unknown IIO_VOLTAGE channel: %d\n", ch);
			return -1;
		}
	} else {
		pr_warn("unknown chan type: %d\n", chan->type);
		return -1;
	}

	return (int)val;
}

static int lombo_gpadc_read_raw(struct iio_dev *indio_dev,
			      struct iio_chan_spec const *chan,
			      int *val, int *val2, long mask)
{
	struct lombo_gpadc_dev *gpadc_dev = iio_priv(indio_dev);

	pr_info("chan channel: %d, name: %s, type: %d\n",
			chan->channel, chan->datasheet_name, chan->type);

	switch (mask) {
	case IIO_CHAN_INFO_RAW:
		*val = lombo_gpadc_read_chan(gpadc_dev, chan);
		if (*val < 0)
			return *val;

		return IIO_VAL_INT;

	default:
		pr_warn("%s unknown mask(%ld)\n", __func__, mask);
		break;
	}

	return -EINVAL;
}

#define LOMBO_GPADC_CHANNEL(_channel, _name, _type)		\
	{							\
		.type = _type,					\
		.indexed = 1,					\
		.channel = _channel,				\
		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),	\
		.datasheet_name = _name,			\
	}

/* Demo */
static const struct iio_chan_spec lombo_adc_channels[] = {
	LOMBO_GPADC_CHANNEL(0, "ain0", IIO_VOLTAGE),
	LOMBO_GPADC_CHANNEL(1, "ain1", IIO_VOLTAGE),
#ifdef CONFIG_ARCH_LOMBO_N9V1
	LOMBO_GPADC_CHANNEL(2, "ain2", IIO_VOLTAGE),
	LOMBO_GPADC_CHANNEL(3, "ain3", IIO_VOLTAGE),
#endif
};

static const struct iio_info lombo_iio_info = {
	.read_raw = lombo_gpadc_read_raw,
};

#ifndef CONFIG_ARCH_LOMBO_N7V1
/* only for using gpadc_ainx as I/O */
void lombo_write_ainx_output(int channel, u32 en)
{
	csp_ain_set_output(gpadc_dev->base, channel, en);
}
EXPORT_SYMBOL_GPL(lombo_write_ainx_output);

/* get gpadc gpio bank value */
int lombo_read_ainx_output(int channel)
{
	int value;

	value = csp_ain_get_output(gpadc_dev->base);
	value = (value & BIT(channel)) >> channel;

	return value;
}
EXPORT_SYMBOL_GPL(lombo_read_ainx_output);
#endif

static irqreturn_t lombo_keydet_irq_handler(int irq, void *data)
{
	u32 pending, key_val;
	int i;
	struct lombo_gpadc_dev *lombo_gpadc = data;

#ifdef CONFIG_ARCH_LOMBO_N7V1
	pending = csp_gpadc_get_int_pend(lombo_gpadc->base);
#else
	pending = csp_ain_get_int_pend(lombo_gpadc->base);
#endif

	for (i = 0; i < lombo_gpadc->ctl_channel.ctl_num; i++) {
		/* ainx threshold pending alive &
		 * ainx threshold interrupt enabled
		 */
		if (pending &
			AIN_X_TH_BIT(lombo_gpadc->ctl_channel.channel[i])) {
			/* recode the key voltage value */
			key_val = csp_ain_get_data(lombo_gpadc->base,
				lombo_gpadc->ctl_channel.channel[i]);

			if ((key_val > lombo_gpadc->key_thre[i].low_thre) &&
			(key_val < lombo_gpadc->key_thre[i].high_thre)) {
				/* report pressed event */
				input_report_key(lombo_gpadc->key_dev,
					BTN_0 + i, 1);
				input_sync(lombo_gpadc->key_dev);
#ifdef CONFIG_ARCH_LOMBO_N7V1
				/* set ain data interrupt */
				csp_gpadc_set_data_int_en(lombo_gpadc->base,
				BIT(gpadc_dev->ctl_channel.channel[i]), 1);

				/* disabled ain threshold interrupt */
				csp_gpadc_set_int_en(lombo_gpadc->base,
				BIT(gpadc_dev->ctl_channel.channel[i]), 0);
#else
				/* set ain data interrupt */
				csp_ain_set_data_int_en(lombo_gpadc->base,
				lombo_gpadc->ctl_channel.channel[i], 1);

				/* disabled ain threshold interrupt */
				csp_ain_set_int_en(lombo_gpadc->base,
				lombo_gpadc->ctl_channel.channel[i], 0);
#endif
			}
		}
	}

#ifdef CONFIG_ARCH_LOMBO_N7V1
	/* clr ain threshold pending && ain data interrupt */
	csp_gpadc_clr_int_pend(lombo_gpadc->base, pending);
#else
	/* clr ain threshold pending && ain data interrupt */
	csp_ain_clr_int_pend(lombo_gpadc->base, pending);
#endif
	for (i = 0; i < lombo_gpadc->ctl_channel.ctl_num; i++) {
		/* get the key voltage value */
		key_val = csp_ain_get_data(lombo_gpadc->base,
				lombo_gpadc->ctl_channel.channel[i]);
		/* data pending alive && threshold not alive &&
		 * key_val > key_high_thre
		 */
		if ((pending &
			AIN_X_DATA_BIT(lombo_gpadc->ctl_channel.channel[i])) &&
		!(pending &
			AIN_X_TH_BIT(lombo_gpadc->ctl_channel.channel[i])) &&
		(key_val > gpadc_dev->key_thre[i].high_thre))  {
			/* report released event */
			input_report_key(lombo_gpadc->key_dev,
					BTN_0 + i, 0);
			input_sync(lombo_gpadc->key_dev);
#ifdef CONFIG_ARCH_LOMBO_N7V1
			/* disabled ain data interrupt */
			csp_gpadc_set_data_int_en(lombo_gpadc->base,
			BIT(gpadc_dev->ctl_channel.channel[i]), 0);

			/* set ain threshold interrupt */
			csp_gpadc_set_int_en(lombo_gpadc->base,
			BIT(gpadc_dev->ctl_channel.channel[i]), 1);
#else
			/* disabled ain data interrupt */
			csp_ain_set_data_int_en(lombo_gpadc->base,
			lombo_gpadc->ctl_channel.channel[i], 0);

			/* set ain threshold interrupt */
			csp_ain_set_int_en(lombo_gpadc->base,
			lombo_gpadc->ctl_channel.channel[i], 1);
#endif
		}
	}

	return IRQ_HANDLED;
}

static void help_info(void)
{
	pr_info("--------- Usage Guide --------------\n");
	pr_info("1. ctl_channel: get the x[0~7] channel voltage\n");
	pr_info("   eg: get channel 2 voltage\n");
	pr_info("   echo 2 > ctl_channel\n");
	pr_info("   cat ctl_channel\n\n");

	pr_info("2. io_channel: used the channel x as GPIO\n");
	pr_info("   eg: used the channel 3 as GPIO, and output 1\n");
	pr_info("   echo 3(channel) 1(value) > io_channel\n");
	pr_info("   get output value(register value): cat io_channel\n");
}

/* io_channel_show - show gpadc channel status */
static ssize_t io_channel_show(struct class *class,
				struct class_attribute *attr, char *buf)
{
	int polarity = 0;

#ifndef CONFIG_ARCH_LOMBO_N7V1
	polarity = csp_ain_get_output(gpadc_dev->base);
#endif
	return sprintf(buf, "0x%x\n", polarity);
}

/* io_channel_store - set gpadc channel high or low */
static ssize_t io_channel_store(struct class *class,
				struct class_attribute *attr,
				const char *buf, size_t size)
{
	char *p_val = NULL;
	u32 channel, en;
	int ret;
#ifndef CONFIG_ARCH_LOMBO_N7V1
	int i;
#endif

	ret = sscanf(buf, "%d", &channel);
	if (ret != 1)
		goto err;

	p_val = strchr(buf, ' ');
	if (p_val == NULL)
		goto err;

	ret = sscanf(p_val + 1, "%d", &en);
	if (ret != 1)
		goto err;

	ret = 0;
#ifndef CONFIG_ARCH_LOMBO_N7V1
	for (i = 0; i < gpadc_dev->io_channel.chan_num; i++) {
		if (channel == gpadc_dev->io_channel.channel[i]) {
			ret = 1;
			break;
		}
	}
	if (ret)
		csp_ain_set_output(gpadc_dev->base, channel, en);
	else {
		PRT_ERR("channel %d is not config as I/O\n", channel);
		goto err;
	}
#endif

	return size;
err:
	help_info();
	return -EINVAL;
}

/* ctl_channel_show - show gpadc channel voltage */
static ssize_t ctl_channel_show(struct class *class,
				struct class_attribute *attr, char *buf)
{
	int voltage, i;
	int ret = 0;

	for (i = 0; i < gpadc_dev->ctl_channel.ctl_num; i++) {
		if (num == gpadc_dev->ctl_channel.channel[i]) {
			ret = 1;
			break;
		}
	}

	if (ret) {
		voltage = csp_ain_get_data(gpadc_dev->base, num);
		voltage = voltage * 1350 * 4 / 0xFFF;
	} else
		voltage = 0;

	return sprintf(buf, "%d mv\n", voltage);
}

/* ctl_channel_store - get gpadc channel that want get voltage */
static ssize_t ctl_channel_store(struct class *class,
				struct class_attribute *attr,
				const char *buf, size_t size)
{
	int ret;

	ret = kstrtoint(buf, 10, &num);
	if (ret  || (num < 0) || (num > CHANNEL_MAX_NUM)) {
		PRT_ERR("Channel number is error, it should be in [0,7]\n");
		help_info();
		return -EINVAL;
	}

	return size;
}

static CLASS_ATTR_RW(io_channel);
static CLASS_ATTR_RW(ctl_channel);

static struct attribute *gpadc_attrs[] = {
	&class_attr_io_channel.attr,
	&class_attr_ctl_channel.attr,
	NULL,
};

ATTRIBUTE_GROUPS(gpadc);

static struct class gpadc_class = {
	.name		= "gpadc",
	.owner		= THIS_MODULE,
	.class_groups	= gpadc_groups,
};

static int lombo_gpadc_probe(struct platform_device *pdev)
{
	int ret, i;
	int vol_max, vol_thre, cnt;
	struct key_threshold k_th[10];
	struct iio_dev *indio_dev;
	struct resource *res;

	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*gpadc_dev));
	if (!indio_dev)
		return -ENOMEM;

	platform_set_drvdata(pdev, indio_dev);
	gpadc_dev = iio_priv(indio_dev);

	indio_dev->name = dev_name(&pdev->dev);
	indio_dev->dev.parent = &pdev->dev;
	indio_dev->info = &lombo_iio_info;
	indio_dev->modes = INDIO_DIRECT_MODE;
	indio_dev->channels = lombo_adc_channels;
	indio_dev->num_channels = ARRAY_SIZE(lombo_adc_channels);

	/* io resource */
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	gpadc_dev->base = ioremap(res->start, resource_size(res));
	/* PRT_INFO("gpadc_dev->base:%px\n", gpadc_dev->base); */
	if (IS_ERR(gpadc_dev->base)) {
		dev_err(&pdev->dev, "err ioremap invalid\n");
		return PTR_ERR(gpadc_dev->base);
	}

	/* clk resource */
	gpadc_dev->gpadc_clk = devm_clk_get(&pdev->dev, "gpadc_clk");
	gpadc_dev->clk_gate = devm_clk_get(&pdev->dev, "apb_gpadc_gate");
	gpadc_dev->clk_reset = devm_clk_get(&pdev->dev, "apb_gpadc_reset");
	gpadc_dev->parent_clk = clk_get(NULL, PARENT_CLK);
	if (IS_ERR(gpadc_dev->gpadc_clk) ||
		IS_ERR(gpadc_dev->clk_gate) ||
		IS_ERR(gpadc_dev->clk_reset) ||
		(IS_ERR(gpadc_dev->parent_clk))) {
		dev_err(&pdev->dev, "get gpadc clock resource failed\n");
		return -EINVAL;
	}

	/* set parent clock */
	clk_set_parent(gpadc_dev->gpadc_clk, gpadc_dev->parent_clk);

	ret = gpadc_enable_clk(gpadc_dev, &pdev->dev);
	if (ret) {
		dev_err(&pdev->dev, "Lombo gpadc_enable_clk failed\n");
		return -EINVAL;
	}

	gpadc_dev->irq = platform_get_irq(pdev, 0);
	if (gpadc_dev->irq < 0) {
		dev_err(&pdev->dev, "err missing IRQ\n");
		goto err_out;
	}

	/* get the number of gpio channels configured on the DT */
	gpadc_dev->io_channel.chan_num =
		of_property_count_u32_elems(pdev->dev.of_node, "io_channel");
	if (of_property_read_u32_array(pdev->dev.of_node, "io_channel",
					gpadc_dev->io_channel.channel,
					gpadc_dev->io_channel.chan_num)) {
		gpadc_dev->io_channel.chan_num = -1;
		dev_info(&pdev->dev, "no channel use as IO");
	}

	/* get the number of gpadc channels configured on the DT */
	gpadc_dev->ctl_channel.ctl_num =
		of_property_count_u32_elems(pdev->dev.of_node,
						"controller_num");
	if (gpadc_dev->ctl_channel.ctl_num < 0)
		goto no_keydet;

	if (of_property_read_u32_array(pdev->dev.of_node, "controller_num",
					gpadc_dev->ctl_channel.channel,
					gpadc_dev->ctl_channel.ctl_num)) {
		dev_err(&pdev->dev, "no controller num property\n");
		goto no_keydet;
	}

	if (of_property_read_u32(pdev->dev.of_node, "vol_max", &vol_max)) {
		dev_err(&pdev->dev, "no controller num property\n");
		gpadc_dev->ctl_channel.ctl_num = -1;
		goto no_keydet;
	}

	if (of_property_read_u32(pdev->dev.of_node, "vol_thre", &vol_thre)) {
		dev_err(&pdev->dev, "no controller num property\n");
		gpadc_dev->ctl_channel.ctl_num = -1;
		goto no_keydet;
	}

	gpadc_dev->vol_thre = VOL_TO_REG(vol_thre, vol_max);
	PRT_INFO("vol_thre = %d, vol_max = %d\n", gpadc_dev->vol_thre, vol_max);

	cnt = of_property_count_u32_elems(pdev->dev.of_node, "key_vol_thre");
	if (cnt <= 0) {
		dev_err(&pdev->dev, "no key_vol_thre config");
		goto no_keydet;
	}
	if ((cnt % 2) || (cnt / 2) > 10) {
		dev_err(&pdev->dev, "key_vol_thre cnt(%d) err", cnt);
		goto no_keydet;
	}
	gpadc_dev->key_num = cnt / 2;

	if (of_property_read_u32_array(pdev->dev.of_node, "key_vol_thre",
					(u32 *)k_th, cnt)) {
		dev_err(&pdev->dev, "read key_vol_thre err");
		goto no_keydet;
	}

	for (i = 0; i < gpadc_dev->key_num; i++) {
		gpadc_dev->key_thre[i].low_thre =
			VOL_TO_REG(k_th[i].low_thre, vol_max);
		gpadc_dev->key_thre[i].high_thre =
			VOL_TO_REG(k_th[i].high_thre, vol_max);
	}

	ret = devm_request_irq(&pdev->dev, gpadc_dev->irq,
				lombo_keydet_irq_handler, IRQF_NO_SUSPEND,
				"lombo-keydet", gpadc_dev);
	if (ret)
		goto err_out;

	/* alloc input device */
	gpadc_dev->key_dev = input_allocate_device();
	if (!gpadc_dev->key_dev) {
		dev_err(&pdev->dev, "input allocate device error\n");
		goto err_out;
	}

	gpadc_dev->key_dev->name = "lombo-key";
	gpadc_dev->key_dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY);

	gpadc_dev->key_dev->keybit[BIT_WORD(BTN_0)] |= BIT_MASK(BTN_0);
	gpadc_dev->key_dev->keybit[BIT_WORD(BTN_1)] |= BIT_MASK(BTN_1);

	ret = input_register_device(gpadc_dev->key_dev);
	if (ret) {
		dev_err(&pdev->dev, "input device register error\n");
		goto err_out;
	}

no_keydet:
#ifdef CONFIG_PM_GENERIC_DOMAINS
	pm_runtime_set_active(&pdev->dev);
	pm_runtime_enable(&pdev->dev);
	pm_runtime_get_noresume(&pdev->dev);
#endif
	if ((gpadc_dev->io_channel.chan_num != -1) ||
		!(gpadc_dev->ctl_channel.ctl_num < 0))
		class_register(&gpadc_class);

	gpadc_init(gpadc_dev->base);

	ret = iio_device_register(indio_dev);
	if (ret) {
		dev_err(&pdev->dev, "unable to register device\n");
		goto err_out;
	}

	PRT_INFO("Lombo GPADC probe success\n");
	return 0;

err_out:
	gpadc_disable_clk(gpadc_dev, &pdev->dev);
	gpadc_deinit(gpadc_dev->base);
	return ret;
}

static int lombo_gpadc_remove(struct platform_device *pdev)
{
	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
	struct lombo_gpadc_dev *gpadc_dev = iio_priv(indio_dev);

	class_unregister(&gpadc_class);

	iio_device_unregister(indio_dev);

	gpadc_deinit(gpadc_dev->base);
	gpadc_disable_clk(gpadc_dev, &pdev->dev);

	return 0;
}

#ifdef CONFIG_PM_SLEEP
static int lombo_gpadc_suspend(struct device *dev)
{
	struct iio_dev *indio_dev = dev_get_drvdata(dev);
	struct lombo_gpadc_dev *gpadc = iio_priv(indio_dev);

#ifndef CONFIG_PM_GENERIC_DOMAINS
	clk_disable_unprepare(gpadc->gpadc_clk);
	clk_disable_unprepare(gpadc->clk_gate);
#endif
	clk_disable_unprepare(gpadc->clk_reset);

	PRT_INFO("----- suspend success! ----\n");
	return 0;
}

static int lombo_gpadc_resume(struct device *dev)
{
	struct iio_dev *indio_dev = dev_get_drvdata(dev);
	struct lombo_gpadc_dev *gpadc = iio_priv(indio_dev);
	int ret;

	ret = clk_prepare_enable(gpadc->clk_reset);
	if (ret) {
		pr_err("clk_prepare_enable gpadc->clk_reset failed\n");
		return ret;
	}
#ifdef CONFIG_PM_GENERIC_DOMAINS
	if (pm_runtime_status_suspended(dev))
		pm_clk_resume(dev);

	/* init gpadc */
	gpadc_init(gpadc->base);

	if (pm_runtime_status_suspended(dev))
		pm_clk_suspend(dev);

#else
	ret = clk_prepare_enable(gpadc->clk_gate);
	if (ret) {
		pr_err("clk_prepare_enable gpadc->clk_gate failed\n");
		return ret;
	}

	ret = clk_prepare_enable(gpadc->gpadc_clk);
	if (ret) {
		pr_err("clk_prepare_enable gpadc->gpadc_clk failed\n");
		return ret;
	}
	/* init gpadc */
	gpadc_init(gpadc->base);

#endif
	PRT_INFO("----- resume success! ----\n");
	return 0;
}
#endif

#ifdef CONFIG_PM
static const struct dev_pm_ops lombo_gpadc_pm_ops = {
#ifdef CONFIG_PM_SLEEP
	SET_SYSTEM_SLEEP_PM_OPS(lombo_gpadc_suspend,
		lombo_gpadc_resume)
#endif
};
#endif

#ifdef CONFIG_OF
static const struct of_device_id lombo_gpadc_dt_match[] = {
	{ .compatible = "lombo,n5-gpadc" },
	{ .compatible = "lombo,n7-gpadc" },
	{ .compatible = "lombotech,n9-gpadc" },
	{},
};
MODULE_DEVICE_TABLE(of, lombo_gpadc_dt_match);
#endif

static struct platform_driver lombo_gpadc_driver = {
	.probe		= lombo_gpadc_probe,
	.remove		= lombo_gpadc_remove,
	.driver		= {
		.name	= DRIVER_NAME,
		.owner	= THIS_MODULE,
	#ifdef CONFIG_OF
		.of_match_table = of_match_ptr(lombo_gpadc_dt_match),
	#endif
	#ifdef CONFIG_PM
		.pm	= &lombo_gpadc_pm_ops,
	#endif
	},
};

static int __init lombo_gpadc_driver_init(void)
{
	return platform_driver_register(&lombo_gpadc_driver);
}
subsys_initcall(lombo_gpadc_driver_init);

static void __exit lombo_gpadc_driver_exit(void)
{
	platform_driver_unregister(&lombo_gpadc_driver);
}
module_exit(lombo_gpadc_driver_exit);

MODULE_DESCRIPTION("LomboTech gpadc driver");
MODULE_AUTHOR("lomboswer@lombotech.com");
MODULE_LICENSE("GPL");
